package controlSoftware.UserThread;

import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import controlSoftware.DataIn.DataIn;
import controlSoftware.DataProcess.DataProcess;
import controlSoftware.DataProcess.Device;
import controlSoftware.DataProcess.SensorData;
import controlSoftware.DataProcess.SysModel;
import controlSoftware.GUI.User;
import controlSoftware.GUI.mainUI;
import controlSoftware.util.Common;
import controlSoftware.util.RecordHandler;

public class UserThread{
	private int id;
	private String ip;
	private int port;
	private RecordHandler record;
	private DataIn dataIn;
	private DataProcess dataProcess;
	private Queue<SensorData> userData;
	private Device deviceObj;	
	private RecordHandler recordmgr;
	
	public static String [] recordFilePaths;
	
	public static Thread mapRenderThread;
	
	/**
	 * user thread constructed with telnet connection
	 * @param id
	 * @param ip
	 * @param port
	 * @param record 
	 */
	public UserThread(int id, String ip, int port, Device device, RecordHandler record){
		this.id = id;
		this.ip = ip;
		this.port = port;
		this.record = record;
		this.deviceObj = device;
		this.recordmgr = record;
		
		// create a new queue to store incoming imu measurements
		this.userData = new ConcurrentLinkedQueue<SensorData>();
		
		// generate new threads for reading and processing data
		this.dataIn = new DataIn(id, "User"+id+"DataIn", userData, ip, port, record);
		this.dataProcess = new DataProcess(id, "User"+id+"DataProcessing", userData, deviceObj);
	}
	
	/**
	 * user thread constructed with mock data input
	 * @param id
	 * @param filePath
	 */
	public UserThread(int id, String filePath, Device device){
		
		// create a new device object, with default user object
		deviceObj = device;
		
		Queue<SensorData> userData = new ConcurrentLinkedQueue<SensorData>();
		
		SysModel.isInitState = true;
		
		dataIn = new DataIn(id, "User"+id+"DataIn", userData, filePath);
		dataProcess = new DataProcess(id, "User"+id+"DataProcessing", userData, deviceObj);
		
		mainUI.usingMockDataFlag = true;
	}
	
	final private static String deviceListSchemaFilepath = "xml/deviceListSchema.xsd";
	
	/**
	 * For real time tracking
	 * Parses the deviceList xml to get device attributes and init a thread for each
	 * If recording the session, create a data file for each device and a session xml
	 */
	public static void initThread() {

		try {
			
			Document doc_record = null;
			Element rootElement = null;
			
			// parses deviceList.xml to obtain all the devices
			File fXmlFile = new File("xml/deviceList.xml");
			SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
			Schema schema = factory.newSchema(new File(deviceListSchemaFilepath));
			Validator validator = schema.newValidator();
			
			DocumentBuilderFactory builderFactory  = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = builderFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
			
	        validator.validate(new DOMSource(doc));
			doc.getDocumentElement().normalize();
			
			// create a new record xml
			if (mainUI.isRecording)
			{
				doc_record = dBuilder.newDocument();
				
				// root element
				rootElement = doc_record.createElement("recordSession");
				doc_record.appendChild(rootElement);
				
				// set attribute to root element
				Attr attr = doc_record.createAttribute("xmlns:xsi");
				attr.setValue("http://www.w3.org/2001/XMLSchema-instance");
				rootElement.setAttributeNode(attr);
			}

			// retrieve all the active devices
			NodeList nActiveDevices = doc.getElementsByTagName("device");
			
			if (mainUI.isRecording)
			{
				recordFilePaths = new String [nActiveDevices.getLength()];
			}
			
			for (int i = 0; i < nActiveDevices.getLength(); i++) 
			{		
				Node nDevice = nActiveDevices.item(i);
				
				if(nDevice.getNodeType() == Node.ELEMENT_NODE)
				{
					
					// Initialize the user object within the user thread device
					User userObj = new User();
					
					// fill in user attributes
					NodeList users= (((Element) nDevice).getElementsByTagName("user"));
					Node user = users.item(0);
					userObj.setsFirstName(Common.getTextValue((Element)user, "firstName"));
					userObj.setsLastName(Common.getTextValue((Element)user, "lastName"));
					userObj.setsIcon(Common.getTextValue((Element)user, "icon"));
					userObj.setsMappedDevice(Integer.valueOf(((Element)nDevice).getAttribute("id")));
					
					System.out.println(userObj.getsFirstName());
						
					Device deviceObj = new Device(Integer.valueOf(((Element)nDevice).getAttribute("id")), userObj);
					
					RecordHandler record = null;
					// copies the information to record xml
					if (mainUI.isRecording)
					{
						// create device element
						Element device = doc_record.createElement("device");
						
						// set attribute to device element
						Attr id = doc_record.createAttribute("id");
						id.setValue(((Element)nDevice).getAttribute("id"));
						device.setAttributeNode(id);
						
						// create and set user element
						Element eUser = doc_record.createElement("user");
						Element first = doc_record.createElement("firstName");
						Element last = doc_record.createElement("lastName");
						Element icon = doc_record.createElement("icon");

						first.appendChild(doc_record.createTextNode(Common.getTextValue((Element)user, "firstName")));
						last.appendChild(doc_record.createTextNode(Common.getTextValue((Element)user, "lastName")));
						icon.appendChild(doc_record.createTextNode(Common.getTextValue((Element)user, "icon")));
						
						eUser.appendChild(first);
						eUser.appendChild(last);
						eUser.appendChild(icon);
						device.appendChild(eUser);
						
						Element filePath = doc_record.createElement("recordFilePath");
						DateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
						String timeStamp = timeFormat.format(new Date());
						String fileName = "record_"+timeStamp+"_device-"+((Element)nDevice).getAttribute("id")+".txt";
		
						filePath.appendChild(doc_record.createTextNode("mockData/"+fileName));
						device.appendChild(filePath);
						
						rootElement.appendChild(device);
						
						record = new RecordHandler("mockData/"+fileName);
					}
					
					// creates a new user thread object
					UserThread user_thread = new UserThread(Integer.valueOf(((Element)nDevice).getAttribute("id"))
							,Common.getTextValue((Element)nDevice, "ip")
							,Common.getIntValue((Element)nDevice, "portNumber")
							,deviceObj
							,record);
					
					
					// add the thread to userPool
					mainUI.userPool.add(user_thread);
					mainUI.listModel.addElement(deviceObj);
				}
				
				if (mainUI.isRecording)
				{	
					// write the content into xml file
					TransformerFactory transformerFactory = TransformerFactory.newInstance();
					Transformer transformer = transformerFactory.newTransformer();
					DOMSource source = new DOMSource(doc_record);
					DateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd_hh-mm-ss");
					String timeStamp = timeFormat.format(new Date());
					StreamResult result = new StreamResult(new File("records/"+timeStamp+".xml"));
					transformer.transform(source, result);
					
					System.out.println("File saved!");
				}
				
			}
			
		} catch (Exception e) {
			System.out.println("Error encountered during deviceList.xml parsing" + e.getMessage());
		}
		
		if(mainUI.userPool.size() > 0){
			mainUI.userPool.start();
			mapRenderThread = new Thread(mainUI.mapRenderer);
			mapRenderThread.start();
		}
	}
	
	/**
	 * initThread for record playback
	 * Parses the specified xml for device attributes and start a thread for each
	 * @param recordFilePath
	 */
	public static void initThread(String recordFilePath) {
		try {	
			// parses deviceList.xml to obtain all the devices
			File fXmlFile = new File(recordFilePath);
//			SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//			Schema schema = factory.newSchema(new File(deviceListSchemaFilepath));
//			Validator validator = schema.newValidator();
//			
			DocumentBuilderFactory builderFactory  = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = builderFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
			
	        //validator.validate(new DOMSource(doc));
			doc.getDocumentElement().normalize();
			
			// retrieve all the active devices
			NodeList nActiveDevices = doc.getElementsByTagName("device");

			for (int i = 0; i < nActiveDevices.getLength(); i++) {
				
				Node nDevice = nActiveDevices.item(i);
				
				if(nDevice.getNodeType() == Node.ELEMENT_NODE)
				{
					
					// Initialize the user object within the user thread device
					User userObj = new User();
					
					// fill in user attributes
					NodeList users= (((Element) nDevice).getElementsByTagName("user"));
					Node user = users.item(0);
					userObj.setsFirstName(Common.getTextValue((Element)user, "firstName"));
					userObj.setsLastName(Common.getTextValue((Element)user, "lastName"));
					userObj.setsIcon(Common.getTextValue((Element)user, "icon"));
					userObj.setsMappedDevice(Integer.valueOf(((Element)nDevice).getAttribute("id")));
					
					System.out.println(userObj.getsFirstName());
						
					Device deviceObj = new Device(Integer.valueOf(((Element)nDevice).getAttribute("id")), userObj);
					
					// creates a new user thread object
					UserThread user_thread = new UserThread(Integer.valueOf(((Element)nDevice).getAttribute("id"))
							,Common.getTextValue((Element)nDevice, "recordFilePath")
							,deviceObj);
	
					// add the thread to userPool
					mainUI.userPool.add(user_thread);
					mainUI.listModel.addElement(deviceObj);
				}
			}	
			
		} catch (Exception e) {
			System.out.println("Error encountered during record xml parsing" + e.getMessage());
		}
		
		
		if(mainUI.userPool.size() > 0){
			mainUI.userPool.start();		
			mapRenderThread = new Thread(mainUI.mapRenderer);
			mapRenderThread.start();
		}
		
	}
	
	/**
	 * start a user thread with data retrieval and processing 
	 */
	public void start(){
		dataIn.start();
		dataProcess.start();
		
		System.out.println("UserThread " + id + " has been started.");
	}
	
	public void stop(){
		mainUI.listModel.clear();

		//stop map renderer thread
		UserThread.mapRenderThread.interrupt();
		
		// clears canvas
		mainUI.mainCanvas.getGraphics().clearRect(0, 0, mainUI.canvas_width, mainUI.canvas_height);
		
		//interrupt data incoming and processing processes
		dataIn.interrupt();
		dataProcess.interrupt();	

		try {
			if (mainUI.isRecording){
				dataIn.getRecordManager().out.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		userData.clear();
		System.out.println("UserThread " + id + " has been stopped.");
	}
	
	public void restart(){
		stop();

		dataIn = new DataIn(id, "User"+id+"DataIn", userData, ip, port, record);
		dataProcess = new DataProcess(id, "User"+id+"DataProcessing", userData, deviceObj);
		
		start();
	}
	
	public Device getDeviceProperties(){
		return deviceObj;
	}
	
	public DataIn getDataInThread(){
		return dataIn;
	}
	
	public DataProcess getDataProcessThread(){
		return dataProcess;
	}
	
	public Queue<SensorData> getUserDataQueue(){
		return userData;
	}

}